package components;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;

import utils.LIFRandomizer;
import utils.StateCamera;
import utils.SynapseRandomizer;
import utils.math_utils.Coordinate;
import utils.math_utils.RandStream;
import utils.math_utils.RandStream.Rand;
import connectors.Connect;

/**
 * Not currently in use as a high-level "Network Class" ought to be. Network
 * instead just contains some utility methods for checking to make sure that 
 * no two neurons occupy the same coordinate in their layout. It also contains
 * methods for initializing large groups of LIF neurons. TODO includes
 * making magic numbers not so magical and making the methods more generalized
 * that is, replacing LIFNeuron with AbstractNeuron. Currently "Magic" numbers
 * are taken from Maass 2002.
 * @author zach
 *
 */
public class Network {
	
	public enum Plane {
		XY, XZ, YZ;
	}
	
	private Layer<? extends AbstractNeuron> fullReservoir;
	private Layer<? extends AbstractNeuron> inputs;
	
	private HashSet<Coordinate> occupiedCoordinates = new HashSet<Coordinate>();
	private HashMap<Coordinate, AbstractNeuron> neuronMap =
			new HashMap<Coordinate, AbstractNeuron>();
	
	public Network (){}	

	//TODO Eliminate magic numbers
	public static LIFNeuron [] initializeNeurons(LIFNeuron [] neurons, int tc,
			double meanRate, double stdRate, double percentInhib, StateCamera camera) {
		int numNeurons = neurons.length;

		//double tc;
		for (int i = 0; i < numNeurons; i++) {
			//tc = RandStream.nextUniform(20, 40);
			if(Math.random() < percentInhib) {
				neurons[i] = new LIFNeuron(tc, false, i);
				neurons[i].setRefractory(2);
			} else {
				neurons[i] = new LIFNeuron(tc, true, i);
				neurons[i].setRefractory(3);
			}
			neurons[i].setPreferredISI(RandStream.nextGaussian(meanRate, stdRate, 5, meanRate*2, true));
			neurons[i].setBgCurrent(13.5);
			neurons[i].setHmP(true);
			if(camera != null) {
				neurons[i].setReporter(true);
				neurons[i].setRecorder(camera);
			}
		}
		
		initializeNeuronVars(neurons);
		return neurons;
	}
	
	//TODO Eliminate magic numbers
	public static LIFNeuron [] initializeNeurons(int numNeurons, int tc,
			double meanRate, double stdRate, double percentInhib, StateCamera camera) {
		LIFNeuron [] neurons = new LIFNeuron[numNeurons];	

		//double tc;
		for (int i = 0; i < numNeurons; i++) {
			//tc = RandStream.nextUniform(20, 40);
			if(Math.random() < percentInhib) {
				neurons[i] = new LIFNeuron(tc, false, i);
				neurons[i].setRefractory(2);
			} else {
				neurons[i] = new LIFNeuron(tc, true, i);
				neurons[i].setRefractory(3);
			}
			neurons[i].setPreferredISI(RandStream.nextGaussian(meanRate, stdRate, 5, meanRate*2, true));
			neurons[i].setBgCurrent(13.5);
			neurons[i].setHmP(true);
			if(camera != null) {
				neurons[i].setReporter(false);
				neurons[i].setRecorder(camera);
			}
		}
		
		initializeNeuronVars(neurons);
		return neurons;
	}
	
	//TODO Eliminate magic numbers
	public static InputNeuron [] initializeInputs(int numInputs, int startIndex) {
		InputNeuron [] neurons = new InputNeuron[numInputs];	
		for (int i = 0; i < numInputs; i++) {
			neurons[i] = new InputNeuron(i + startIndex);
			neurons[i].setReporter(false);
			neurons[i].setLastSpike(0);
		}
		return neurons;
	}
	
	//TODO Eliminate magic numbers
	public static void initializeNeuronVars (LIFNeuron [] neurons) {
		
		for (int i = 0, n = neurons.length; i < n; i++) {
			neurons[i].setThreshold(15);
			neurons[i].setLastSpike(0);
			neurons[i].setTime(0);
			neurons[i].setReset(13.5);
			neurons[i].setMemResistance(1);
			neurons[i].setRestPotential(0);	
			neurons[i].setMemPotential(RandStream.nextUniform(13.5, 15.1));	
		}		
		
	}
	
	/** 
	 * Values from Maass (2002), D & F in mSecs
	 */
	public static void initializeSynRandomizers (Connect con) {
		
		SynapseRandomizer eeSR = new SynapseRandomizer();
		eeSR.setU(new RandStream(Rand.GAUSS, 0.5, 0.25, 0.001,
				Double.MAX_VALUE, true));
		eeSR.setD(new RandStream(Rand.GAUSS, 1100, 550, 0.001,
				Double.MAX_VALUE, true));
		eeSR.setF(new RandStream(Rand.GAUSS, 50, 25, 0.001,
				Double.MAX_VALUE, true));
		
		
		SynapseRandomizer eiSR = new SynapseRandomizer();
		eiSR.setU(new RandStream(Rand.GAUSS, 0.05, 0.025, 0.001,
				Double.MAX_VALUE, true));
		eiSR.setD(new RandStream(Rand.GAUSS, 125, 62.5, 0.001,
				Double.MAX_VALUE, true));
		eiSR.setF(new RandStream(Rand.GAUSS, 120, 60, 0.001,
				Double.MAX_VALUE, true));
		
		
		SynapseRandomizer ieSR = new SynapseRandomizer();
		ieSR.setU(new RandStream(Rand.GAUSS, 0.25, 0.125, 0.001,
				Double.MAX_VALUE, true));
		ieSR.setD(new RandStream(Rand.GAUSS, 700, 350, 0.001,
				Double.MAX_VALUE, true));
		ieSR.setF(new RandStream(Rand.GAUSS, 20, 10, 0.001,
				Double.MAX_VALUE, true));
		
		
		SynapseRandomizer iiSR = new SynapseRandomizer();
		iiSR.setU(new RandStream(Rand.GAUSS, 0.32, 0.16, 0.0001,
				Double.MAX_VALUE, true));
		iiSR.setD(new RandStream(Rand.GAUSS, 144, 72, 0.001,
				Double.MAX_VALUE, true));
		iiSR.setF(new RandStream(Rand.GAUSS, 60, 30, 0.001,
				Double.MAX_VALUE, true));
		
		con.setEeSR(eeSR);
		con.setEiSR(eiSR);
		con.setIeSR(ieSR);
		con.setIiSR(iiSR);
		
	}
	
	
	/** 
	 * Values from Maass (2002), D & F in mSecs
	 */
	public static Connect initializeSynRandomizers () {
		
		SynapseRandomizer eeSR = new SynapseRandomizer();
		eeSR.setU(new RandStream(Rand.GAUSS, 0.5, 0.25, 0.001,
				Double.MAX_VALUE, true));
		eeSR.setD(new RandStream(Rand.GAUSS, 1100, 550, 0.001,
				Double.MAX_VALUE, true));
		eeSR.setF(new RandStream(Rand.GAUSS, 50, 25, 0.001,
				Double.MAX_VALUE, true));
		
		
		SynapseRandomizer eiSR = new SynapseRandomizer();
		eiSR.setU(new RandStream(Rand.GAUSS, 0.05, 0.025, 0.001,
				Double.MAX_VALUE, true));
		eiSR.setD(new RandStream(Rand.GAUSS, 125, 62.5, 0.001,
				Double.MAX_VALUE, true));
		eiSR.setF(new RandStream(Rand.GAUSS, 120, 60, 0.001,
				Double.MAX_VALUE, true));
		
		
		SynapseRandomizer ieSR = new SynapseRandomizer();
		ieSR.setU(new RandStream(Rand.GAUSS, 0.25, 0.125, 0.001,
				Double.MAX_VALUE, true));
		ieSR.setD(new RandStream(Rand.GAUSS, 700, 350, 0.001,
				Double.MAX_VALUE, true));
		ieSR.setF(new RandStream(Rand.GAUSS, 20, 10, 0.001,
				Double.MAX_VALUE, true));
		
		
		SynapseRandomizer iiSR = new SynapseRandomizer();
		iiSR.setU(new RandStream(Rand.GAUSS, 0.32, 0.16, 0.0001,
				Double.MAX_VALUE, true));
		iiSR.setD(new RandStream(Rand.GAUSS, 144, 72, 0.001,
				Double.MAX_VALUE, true));
		iiSR.setF(new RandStream(Rand.GAUSS, 60, 30, 0.001,
				Double.MAX_VALUE, true));
		
		return new Connect(eeSR, eiSR, ieSR, iiSR);
		
	}
	
	public ArrayList<LIFNeuron> createLIFLayer(int[] start, int index,
			int dim1, int dim2, LIFRandomizer nr, float exRatio, Plane p) {
		
		int a;
		int b;
		switch(p) {
			case XY: a = 0; b = 1; break;
			case XZ: a = 0; b = 2; break;
			case YZ: a = 1; b = 2; break;
			default: a = 0; b = 1; break;
		}	
		
		ArrayList<LIFNeuron> nList = new ArrayList<LIFNeuron>();
		int [] current = start;
		checkCollision(new Coordinate(start));
		boolean ex;
		for (int i = start[0]; i < dim1 + start[0]; i++) {
			for (int j = start[1]; j < dim2 + start[1]; j++) {
				
				if (Math.random() <= exRatio) {
					ex = true;
				} else {
					ex = false;
				}
				LIFNeuron n = new LIFNeuron(ex,index);
				nr.randomizeNeuron(n);
				Coordinate c = new Coordinate(current);
				checkCollision(c);
				occupiedCoordinates.add(c);
				n.setCoordinates(c);
				nList.add(n);
				current[a]++;
				current[b]++;
				index++;
			}
		}
		
		return nList;
		
	}
	
	public void cubicLayout(Coordinate o, int[] dims,
			AbstractNeuron [] neurons) 
					throws IllegalArgumentException {
		checkCollision(o);
		int index = 0;
		Coordinate current;
		System.out.println(neurons.length);
		for(int z = o.getZ(); z < dims[2] + o.getZ(); z++) {
			for(int y = o.getY(); y < dims[1] + o.getY(); y++) {
				for (int x = o.getX(); x < dims[0] + o.getX(); x++) {
					if(index < neurons.length) {
						current  = new Coordinate(x, y, z);
						checkCollision(current);
						occupiedCoordinates.add(current);
						neurons[index].setCoordinates(x, y, z);
						neuronMap.put(current, neurons[index]);
						index++;
					} else {
						x = Integer.MAX_VALUE;
						y = Integer.MAX_VALUE;
						z = Integer.MAX_VALUE;
					}
				}
			}
		}
	}
	
	public void cubicLayoutUnsafe(Coordinate o, int[] dims,
			LIFNeuron [] neurons) 
					throws IllegalArgumentException {
		checkCollision(o);
		int index = 0;
		Coordinate current;
		for(int z = o.getZ(); z < dims[2] + o.getZ(); z++) {
			for(int y = o.getY(); y < dims[1] + o.getY(); y++) {
				for (int x = o.getX(); x < dims[0] + o.getX(); x++) {
					if(index < neurons.length) {
						current  = new Coordinate(x, y, z);
						occupiedCoordinates.add(current);
						neurons[index].setCoordinates(x, y, z);	
						index++;
					} else {
						x = Integer.MAX_VALUE;
						y = Integer.MAX_VALUE;
						z = Integer.MAX_VALUE;
					}
				}
			}
		}
	}
	
	
	public void checkCollision(Coordinate c) {
		if(occupiedCoordinates.contains(c)) {
			System.out.println("I happen");
			throw new IllegalArgumentException("Collision");
		}
	}
	
	public void checkCollision(Coordinate start, int [] dims) {
		checkCollision(start);
		
		for(int x = 0; x < dims[0]; x++){
			for(int y = 0; y < dims[1]; y++){
				for(int z = 0; z < dims[2]; z++){
					Coordinate c = new Coordinate(x, y, z);
					checkCollision(c);
				}
			}
		}
		
	}

	public Layer<? extends AbstractNeuron> getFullReservoir() {
		return fullReservoir;
	}

	public void setFullReservoir(Layer<? extends AbstractNeuron> fullReservoir) {
		this.fullReservoir = fullReservoir;
	}
	
	public Layer<? extends AbstractNeuron> getInputs() {
		return inputs;
	}

	public void setInputs(Layer<? extends AbstractNeuron> inputs) {
		this.inputs = inputs;
	}

	public HashMap<Coordinate, AbstractNeuron> getNeuronMap(){
		return neuronMap;
	}
	
}
